#include "setting.h"
#include <unistd.h>
#include <fstream>
#include "sys.h"

#define CONFIG_FILE "./tmp/sysconfig"
#define WIFI_SEARCK_CMD "iwlist wlan0 scan"
#define WIFI_STATE_CMD "iwconfig wlan0"
#define NET_GET_INFO_CMD "ifconfig wlan0"
#define NET_DOWN "ifconfig wlan0 down"
#define NET_UP "ifconfig wlan0 up"

#define WPA_ADD_NETWORK "wpa_cli -i wlan0 add_network"
#define WPA_SET_NETWORK "wpa_cli -i wlan0 set_network"
#define WPA_SELECT_NETWORK "wpa_cli -i wlan0 select_network"
#define WPA_ENABLE_NETWORK "wpa_cli -i wlan0 enable_network"
#define WPA_DISABLE_NETWORK "wpa_cli -i wlan0 disable_network"
#define WPA_STATUS "wpa_cli -i wlan0 status"
#define WPA_UDHCPC "udhcpc -i wlan0 -q"
#define WPA_SAVE_CONFIG "wpa_cli -i wlan0 save_config"

static int thread_func(Setting *obj);
static void poweroff_func(void);
static void reboot_func(void);
static void wlan_scan(void);
static void wlan_scan_func(void);
static int wlan_wpa_search(string &ssid, string &psk);
static void wlan_connect(int id, const char *_ssid, const char *_psk);
static void wlan_wpa_change_psk(int id, const char *psk);
static void wlan_wpa_disconnect(int id);
static int wlan_wpa_add_network(string &ssid, string &psk);

static Setting *setting;

static mutex *ui_mutex = nullptr;

static const char *net_info_header[]=
{
    "HWaddr ",
    "inet addr:", 
    "RX packets:", 
    "TX packets:", 
    "RX bytes:", 
    "TX bytes:", 
};

static const char *config_data_header[] = 
{
    "brightness:",
    "volume:",
    "auto_position:",
    "mainbg:",
    "host_ip:",
    "weather_appid:",
    "weather_appsecret:",
    "weather_city:",
};

Setting::Setting(void (*exit_cb)(lv_event_t *), mutex &UIMutex)
{
    thread_exit_flag = false;
    ui_mutex = &UIMutex;

    gui_opt_t gui_opts = {0};

    gui_opts.exit_cb = exit_cb;
    gui_opts.set_brightness = sys_set_brightness;
    gui_opts.get_brightness = sys_get_brightness;
    gui_opts.poweroff = poweroff_func;
    gui_opts.reboot = reboot_func;
    gui_opts.wlan_scan = wlan_scan;
    gui_opts.connect_ap = wlan_connect;
    gui_opts.change_pwd = wlan_wpa_change_psk;
    gui_opts.disconnect_ap = wlan_wpa_disconnect;

    setting_gui_init(&gui_opts);
    setting = this;         //保存对象

    pthread = new std::thread(thread_func, this);
}

Setting::~Setting()
{
    thread_exit_flag = true;
    pthread->join();
    delete pthread;

    setting_gui_release();
}

/*
 *@brief 读取设置信息
 *@param config 保存设置信息
 *@return true - 读取到了配置信息  false - 缺省值配置信息
 */
bool Setting::readconfig(sysconfig &config)
{
    ifstream file;

    file.open(CONFIG_FILE, ios::in);

    if(file.is_open() != true)
    {
        config.brightness = 50;        //缺省值
        config.volume = 50;
        config.auto_position = 0;
        config.mainbg_file = "mainbg_1.bin";
        config.host_ip = "127.0.0.1";
        config.weather_appid = "";
        config.weather_appsecret = "";
        config.weather_city = "北京";

        return false;
    }

    char *buf = new char[2048];
    memset(buf, 0, 2048);
    file.read(buf, 2048);
    file.close();

    string config_string(buf);
    delete[] buf;

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;
    
    int *value[] = {&config.brightness, &config.volume, &config.auto_position};
    string *str[] = {&config.mainbg_file, &config.host_ip, 
                                &config.weather_appid, &config.weather_appsecret, &config.weather_city};

    for(int i = 0; i < 8; i++)
    {
        dataStart = config_string.find(config_data_header[i], dataEnd) + strlen(config_data_header[i]); // 寻找前导字符串
        dataEnd = config_string.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)
        {
            dataStr = config_string.substr(dataStart, dataEnd - dataStart);
            if(i >= 3)
                *(str[i - 2]) = dataStr;
            else
                *(value[i]) = stoi(dataStr);
        }
    }

    return true;
}

/*
 *@brief 保存设置信息
 *@param config 保存的设置信息
 */
void Setting::saveconfig(sysconfig &config)
{
    string s = "brightness:" + to_string(config.brightness) + "\n";

    s += "volume:" + to_string(config.volume) + "\n";
    s += "auto_position:" + to_string(config.auto_position) + "\n";
    s += "mainbg:" + config.mainbg_file + "\n";
    s += "host_ip:" + config.host_ip + "\n";
    s += "weather_appid:" + config.weather_appid + "\n";
    s += "weather_appsecret:" + config.weather_appsecret + "\n";
    s += "weather_city:" + config.weather_city + "\n";

    ofstream file;

    file.open(CONFIG_FILE, ios::out);       //写方式打开文件

    file << s << endl;

    file.close();
}

/*
 *@brief 更新网络连接状态
 *@return 返回已连接的wifi ssid，未连接则返回"null"
 */
string &Setting::updateConnection(void)
{
    FILE *fp;
    char *buff = new char[2048];
    memset(buff, 0, 2048);
    connected_ssid = "null";      

    do
    {
        fp = popen(WIFI_STATE_CMD, "r");       //执行shell命令
        if(fp == NULL)
            break;
        
        fread(buff, 1, 2048, fp);        //读取shell命令返回内容
        string str(buff);

        size_t dataStart = 0;
        size_t dataEnd = 0;

        dataStart = str.find("ESSID:\"", dataEnd);     //寻找ssid字串
        if(dataStart != string::npos)
        {
            dataStart += strlen("ESSID:\"");
            dataEnd = str.find('\"', dataStart);                      // 寻找 "
            if(dataEnd != string::npos)
                connected_ssid = str.substr(dataStart, dataEnd - dataStart);
        }

    }while(0);

    delete[] buff;
    pclose(fp);

    return connected_ssid;
}

/*
 *@brief 更新网络信息并添加到ui显示
 *@return 成功返回true，失败返回false
 */
bool Setting::getNetInfo(void)
{
    FILE *fp;

    fp = popen(NET_GET_INFO_CMD, "r");       //执行shell命令
    if(fp == NULL)
        return false;
    
    char *buff = new char[2048];      
    fread(buff, 1, 2048, fp);        //读取shell命令返回内容
    pclose(fp);

    string str(buff);
    NetInfo *info = (NetInfo *)calloc(sizeof(NetInfo), 1);

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;
    char *array[] = {info->address, info->ip_addr};
    int *value[] = {&info->rx_packets, &info->tx_packets, &info->rx_bytes, &info->tx_bytes};

    //解析字符串
    for(int i = 0; i < 6; i++)
    {
        dataStart = str.find(net_info_header[i], dataEnd) + strlen(net_info_header[i]);     //寻找前导字串
        dataEnd = str.find(' ', dataStart);                      // 寻找空格
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            if(i < 2)
                strcpy(array[i], dataStr.c_str());
            else
                *(value[i - 2]) = stoi(dataStr);
        }
    }
    strcpy(info->essid, connected_ssid.c_str());

    //更新到UI界面
    ui_mutex->lock();
    setting_gui_update_net_info(info);
    ui_mutex->unlock();

    free(info);
    delete[] buff;

    return true;
}

/*
 *@brief 搜索wifi并添加到wifi列表
 *@return 成功返回true，失败返回false
 */
bool Setting::wifiSearch(void)
{
    FILE *fp;

    fp = popen(WIFI_SEARCK_CMD, "r");       //执行shell命令
    if(fp == NULL)
        return false;
    
    char *buff = new char[1024 * 100];      //1个wifi信息大约1kB
    fread(buff, 1, 1024 * 100, fp);        //读取shell命令返回内容

    pclose(fp);

    string str(buff);
    WifiInfo *info = (WifiInfo *)calloc(sizeof(WifiInfo), 1);

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;

    //解析字符串
    while((dataStart = str.find("Cell", dataEnd)) != string::npos)      //寻找wifi cell
    {
        dataStart = str.find("Address: ", dataEnd) + strlen("Address: ");     //寻找物理地址字串
        dataEnd = str.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            strcpy(info->address, dataStr.c_str());
        }

        dataStart = str.find("Frequency:", dataEnd) + strlen("Frequency:");     //寻找频段字串
        dataEnd = str.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            strcpy(info->freq, dataStr.c_str());
        }

        dataStart = str.find("Quality=", dataEnd) + strlen("Quality=");     //寻找信号强度字串
        dataEnd = str.find("/70", dataStart);                      // 寻找单位
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            info->level = atoi(dataStr.c_str()) * 100 / 70;
        }

        dataStart = str.find("Encryption key:", dataEnd) + strlen("Encryption key:");     //寻找密钥使能字串
        dataEnd = str.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            info->encryption_key = dataStr == "on" ? true : false;
        }

        dataStart = str.find("ESSID:\"", dataEnd) + strlen("ESSID:\"");     //寻找ssid字串
        dataEnd = str.find('\"', dataStart);                      // 寻找 "
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            strcpy(info->essid, dataStr.c_str());
        }

        dataStart = str.find("Bit Rates:", dataEnd) + strlen("Bit Rates:");     //寻找比特率字串
        dataEnd = str.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            strcpy(info->bit_rates, dataStr.c_str());
        }

        string ssid = string(info->essid);
        string psk;

        info->id = wlan_wpa_search(ssid, psk);          //寻找是否是已保存的wifi
        strcpy(info->psk, psk.c_str());     //写入密码

        info->is_connected = connected_ssid == string(info->essid) ? true : false;

        //添加到wifi列表
        setting_gui_list_add_wifi(info);

    }

    free(info);
    delete[] buff;

    return true;
}

static int thread_func(Setting *obj)
{
    usleep(50000);

    while (!obj->getExitFlag())
    {
        obj->updateConnection();        //更新连接wifi信息
        obj->getNetInfo();      //更新网络信息
        
        usleep(500000);
    }

    return 0;
}

/*
 *@brief 修改已保存的wifi密码
 *@param id 已保存的wifi id
  *@param psk 新的密码
 */
static void wlan_wpa_change_psk(int id, const char *psk)
{
    if(psk == NULL)
        return;

    char *cmd = new char[256];

    sprintf(cmd, "%s %d psk \'\"%s\"\'", WPA_SET_NETWORK, id, psk);
    system(cmd);            //执行命令修改密码

    sprintf(cmd, "%s", WPA_SAVE_CONFIG);
    system(cmd);            //执行命令保存修改

    delete[] cmd;
}

/*
 *@brief 保存新的wifi
 *@param ssid wifi帐号
  *@param psk 有密码则设置为密码，无则设置为"none"
 *@return 添加成功返回id，添加失败返回-1
 */
static int wlan_wpa_add_network(string &ssid, string &psk)
{
    FILE *fp;
    int id = -1;
    char *buff = new char[256];

    fp = popen(WPA_ADD_NETWORK, "r");       //执行shell命令，添加新的wifi
    if(fp != NULL)
    {
        fread(buff, 1, 256, fp);        //读取shell命令返回内容
        pclose(fp);

        string str(buff);
        if(!str.empty())     
            id = stoi(str);         //获取wifi id
    }

    printf("\n\nid=%d\n\n", id);

    if(id >= 0)
    {
        sprintf(buff, "%s %d ssid  \'\"%s\"\'", WPA_SET_NETWORK, id, ssid.c_str());
        system(buff);            //设置ssid

        if(psk != "none")
            sprintf(buff, "%s %d psk  \'\"%s\"\'", WPA_SET_NETWORK, id, psk.c_str());
        else
            sprintf(buff, "%s %d key_mgmt NONE", WPA_SET_NETWORK, id);
            
        system(buff);            //设置密码

        sprintf(buff, "%s %d scan_ssid  1", WPA_SET_NETWORK, id);
        system(buff);            //设置scan_ssid

        sprintf(buff, "%s", WPA_SAVE_CONFIG);
        system(buff);            //执行命令保存修改
    }

    delete[] buff;

    return id;
}

/*
 *@brief 扫描/etc/wpa_supplicant.conf文件是否是已保存的wifi
 *@param ssid wifi帐号
  *@param psk 有密码则会设置为密码，无则会设置为"none"
 *@return 匹配成功返回id，匹配失败返回-1
 */
static int wlan_wpa_search(string &ssid, string &psk)
{
    ifstream file;
    int id = 0;
    psk = "none";

    file.open("/etc/wpa_supplicant.conf", ios::in);

    if(file.is_open() != true)
    {
        id= -1;
        return id;
    }

    char *buf = (char *)calloc(40960, 1);
    file.read(buf, 40960);
    file.close();

    string str(buf);
    free(buf);

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;

    //解析字符串
    while((dataStart = str.find("network", dataEnd)) != string::npos)      //寻找network
    {
        dataStart = str.find("ssid=\"", dataEnd) + strlen("ssid=\"");     //寻找ssid字串
        dataEnd = str.find('\"', dataStart);                      // 寻找 "
        if(dataEnd != string::npos)         //找到ssid
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            if(dataStr == ssid)         //ssid匹配成功
            {
                dataStart = str.find("psk=\"", dataEnd) + strlen("psk=\"");     //寻找psk字串
                dataEnd = str.find('\"', dataStart);                      // 寻找 "
                if(dataEnd != string::npos)         //找到密码
                {
                    dataStr = str.substr(dataStart, dataEnd - dataStart);
                    psk = dataStr;
                }

                return id;
            }
        }

        id++;
    }

    return -1;
}

/*
 *@brief 关闭指定连接
 *@param id 已保存的wifi id，为-1则表示是全新的wifi
 */
static void wlan_wpa_disconnect(int id)
{
    if(id < 0)
        return;

    char *cmd = new char[256];

    sprintf(cmd, "%s %d", WPA_DISABLE_NETWORK, id);
    system(cmd);            //执行命令关闭连接
    setting_gui_connect_over(false);

    delete[] cmd;
}

/*
 *@brief 连接指定的wifi
 *@param id 已保存的wifi id，为-1则表示是全新的wifi
 *@param ssid wifi帐号，只有id为-1使用到
  *@param psk 有密码则设置为密码，无则设置为"none"， 只有id为-1使用到
 *@return 匹配成功返回id，匹配失败返回-1
 */
static void wlan_connect_func(int id, const char *_ssid, const char *_psk)
{
    if(_ssid == NULL || _psk == NULL)
        return;
    
    if(id < 0)          //未保存的wifi
    {
        string ssid(_ssid);
        string psk(_psk);

        id = wlan_wpa_add_network(ssid, psk);
    }

    char *cmd = new char[64];

    ui_mutex->lock();
    setting_gui_connecting_cont_create();           //通知UI开始连接
    ui_mutex->unlock();

    sprintf(cmd, "%s %d", WPA_SELECT_NETWORK, id);      //连接热点
    system(cmd);

    sprintf(cmd, "%s %d", WPA_ENABLE_NETWORK, id);     //使能热点
    system(cmd);

    delete[] cmd;

    int tick = 0;
    char *buff = new char[4096]; 

    while(tick < 10)        //超时时间10 ×1s = 10s
    {
        FILE *fp;
        fp = popen(WPA_STATUS, "r");       //执行shell命令，查询wifi连接状态
        if(fp == NULL)
            continue;
            
        memset(buff, 0, 4096);
        fread(buff, 1, 4096, fp);        //读取shell命令返回内容
        pclose(fp);

        string state(buff);
        size_t dataStart = 0;
        size_t dataEnd = 0;
        string dataStr;

        dataStart = state.find("wpa_state=", dataEnd) + strlen("wpa_state=");     //寻找state字串
        dataEnd = state.find('\n', dataStart);                      // 寻找换行符
        if(dataEnd != string::npos)         //找到字段
        {
            dataStr = state.substr(dataStart, dataEnd - dataStart);
            if(dataStr == "COMPLETED")          //连接成功状态
                break;
        }
        sleep(1);
        tick++;
    }
    

    if(tick >= 10)
    {
        //告知UI连接失败
        ui_mutex->lock();
        setting_gui_connect_set_status(false);
        setting_gui_connect_over(false);         //告知UI连接动作结束
        ui_mutex->unlock();

        delete[] buff;
        return;
    }

    ui_mutex->lock();
    setting_gui_connect_set_status(true);           //告知UI连接成功
    ui_mutex->unlock();

    FILE *fp;
    fp = popen(WPA_UDHCPC, "r");       //执行shell命令，获取IP地址与dns地址
    if(fp == NULL)
    {
        //告知获取IP地址失败
        ui_mutex->lock();
        setting_gui_get_ip_set_status(false);
        setting_gui_connect_over(false);
        ui_mutex->unlock();

        delete[] buff;
        return;
    }
    memset(buff, 0, 4096);
    fread(buff, 1, 4096, fp);        //读取shell命令返回内容
    pclose(fp);

    string state(buff);
    printf("\nreceive:\n%s\n", state.c_str());
    size_t strIndex = 0;
    bool status = false;

    strIndex = state.find("setting default routers", 0);     //寻找获取IP地址前导字串
    ui_mutex->lock();
    if(strIndex != string::npos)         //找到字段
    {
        setting_gui_get_ip_set_status(true);       //告知UI获取IP地址成功
        status = true;
    }
    else
        setting_gui_get_ip_set_status(false);        //告知UI获取IP地址失败

    setting_gui_connect_over(status);         //告知UI连接动作结束

    ui_mutex->unlock();
    delete[] buff;
}

static void wlan_connect(int id, const char *_ssid, const char *_psk)
{
    thread wlan_connect_thread(wlan_connect_func, id, _ssid, _psk);     //创建线程连接wifi，不会导致UI卡死
    wlan_connect_thread.detach();       
}

static void wlan_scan(void)
{
    thread wlan_scan_thread(wlan_scan_func);        //创建线程搜索wifi，不会导致UI卡死

    wlan_scan_thread.detach();      //线程分离
}

static void wlan_scan_func(void)
{
    setting->wifiSearch();          //搜索wifi
}

static void poweroff_func(void)
{
    sys_set_brightness(0);      //关闭背光
    system("poweroff");
}

static void reboot_func(void)
{
    sys_set_brightness(0);      //关闭背光
    system("reboot");
}

